iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 29 篇

Day 29|現在遇到 Bug,我不再第一時間改程式碼了

  • 分享至 

  • xImage
  •  

這 28 天一路寫下來,我發現自己最大的改變,不是突然記住了多少語法。

而是:

遇到問題時,我開始不會第一時間衝去改 Code。

以前看到 Bug,我很容易直接進入:

哪一行錯了?
↓
要改哪個 if?
↓
是不是補一個判斷就好?

但後來越來越常發現:

Bug 出現的位置,不一定就是 Root Cause 所在的位置。

有時候畫面錯了,真正原因在 API。

有時候 API 打錯了,真正原因在 Route。

有時候 Request 重複打,真正原因在 Component remount。

有時候 Input 顯示怪怪的,真正原因是兩個 State 在搶著控制它。

所以現在遇到 Bug,我開始先做一件事:

把流程畫出來。


第一步:先重現,不要先猜

以前看到:

「這裡有 Bug。」

我很容易直接看 Code。

但如果連 Bug 到底在什麼操作下發生都沒有確認,

其實很容易從錯的方向開始。

所以我現在會先問:

怎麼操作才會發生?

每次都發生嗎?

只有某些資料會發生嗎?

第一次正常、第二次才壞嗎?

重新整理後還會發生嗎?

例如:

進列表
↓
選 Filter
↓
進 Detail
↓
返回列表
↓
API 400

這和:

第一次進列表
↓
直接 API 400

其實是完全不同的問題。

前者很可能和:

URL 還原
State 恢復
Serialization

有關。

所以:

先把重現步驟寫清楚,本身就是 Debug 的第一步。


第二步:先畫「正常情況應該怎麼走」

例如一個 Search:

使用者輸入
↓
Form
↓
Submit
↓
整理 Params
↓
Service
↓
Request
↓
Backend
↓
Response
↓
State
↓
Table

如果畫面出問題,

我就可以問:

到底是哪一站開始跟預期不一樣?

這比直接盯著 500 行 Component 有用很多。


第三步:找 Source of Truth

很多 Bug 的核心其實是:

到底哪一份資料才是真的?

例如:

Form 裡有一份
State 裡有一份
URL 裡又有一份
API Response 又一份

假設畫面顯示:

ABC

我會開始問:

ABC 是從 Props 來的?

State?

Form?

URL?

還是 API Response?

只要找到:

Source of Truth

後面的資料流通常就比較容易追。


第四步:確認「值」以外,也確認「型別」

以前我很常只看:

value = FA

覺得:

值看起來沒錯啊。

但後來才發現:

"FA"

和:

["FA"]

人眼看起來很像。

程式來說卻完全不同。

所以現在除了問:

值是多少?

還會問:

它現在是 String?
Array?
Object?
null?
undefined?

尤其資料跨過:

Form
URL
Request
Response

這些邊界時,

型別很容易改變。


第五步:看 Network,不要只相信自己組出來的 Payload

假設前端寫:

const payload = {
  description: values.description
}

我不能只看:

console.log(payload)

然後就說:

有送啊。

因為真正重要的是:

瀏覽器最後送出去的 Request 到底是什麼?

所以我現在會看:

DevTools
↓
Network
↓
Request Payload

確認:

欄位真的存在嗎?

是 null?

是 ""?

還是根本被省略?

例如:

{
  description: undefined
}

最後 JSON 可能根本沒有:

description

這種差異如果只看 UI,

很難發現。


第六步:Request 和 Response 要分開看

假設:

PATCH 成功
Status 200

以前我很容易直接理解成:

修改成功。

但現在會拆開:

Request 有送對嗎?
↓
Backend 有接受嗎?
↓
真的有更新 Database 嗎?
↓
下一次 GET 回來是什麼?

因為:

200

只能告訴我:

這次 HTTP Request 被系統成功處理。

不一定代表:

我想改的欄位真的以我預期的方式被保存。


第七步:用 Response 把問題切成前端或後端

這招對我來說非常實用。

假設畫面顯示:

舊資料

我會先看 GET Response。

如果 Response 已經是新的

例如:

{
  "name": "New Name"
}

但畫面還是:

Old Name

那問題比較可能在:

State
Form
Render
dataSource
Memo

也就是前端這一側。


如果 Response 本身就是舊的

例如:

{
  "name": "Old Name"
}

那就不用一直改 React Render。

可以繼續往:

Backend
Database
Cache

追。

這個切法可以省很多亂猜的時間。


第八步:不要看到 API 問題,就只查 API

這幾天其實遇過很多這種例子。

Request 重複打

表面:

API 打太多

真正原因可能是:

Component 被 unmount
↓
再 mount
↓
初始化 Request 又跑

API 收到奇怪 ID

表面:

Backend 收到 create 當 ID

真正原因可能是:

Route Match 錯
↓
create 被當成 :id

API 400

表面:

Backend 不接受參數

真正原因可能是:

Array
↓
URL
↓
String
↓
回來沒轉回 Array

所以我現在比較少問:

哪一支 API 壞了?

而會先問:

這個 Request 為什麼會變成現在這個樣子?


第九步:找到「第一個不符合預期的地方」

這可能是我現在最常用的 Debug 問題。

假設:

Input
↓
Form
↓
Payload
↓
API
↓
Response
↓
State
↓
UI

最後 UI 錯了。

不要一次看全部。

而是一站一站問:

Input 正確嗎?
↓
Yes

Form Value 正確嗎?
↓
Yes

Payload 正確嗎?
↓
No

那就停。

因為:

第一個錯誤已經出現在 Payload。

後面的:

API
Response
UI

即使全部錯,

也可能只是被前面的錯誤一路帶下去。


我現在會畫成這樣

User Action
↓
UI Event
↓
State / Form
↓
Transform
↓
Request
↓
Backend
↓
Database
↓
Response
↓
State
↓
Render

然後:

✓
✓
✓
✗

找到第一個:

✗

通常就已經離 Root Cause 很近。


第十步:一次只驗證一個假設

以前 Debug 很容易:

改 A
改 B
加 C
順便重構 D

最後:

欸,好了。

但這時候反而不知道:

到底是哪一個改動修好的?

所以現在我比較想做到:

提出一個假設
↓
只做最小修改
↓
重新測試
↓
確認假設成立或不成立

例如:

我懷疑 Filter 收合會造成 remount。

那先不要改 Request。

可以先:

加 mount / unmount log

確認:

真的有 remount 嗎?

如果沒有,

就把這個假設排除。

這比:

先加 cache 看看

更容易找到真正原因。


Workaround 和 Root Fix 也不一樣

假設 API 重複打。

我可以:

加一個 flag
↓
第二次不要 Request

這可能:

功能看起來好了。

但如果真正原因是:

Component 不該一直 remount

那這個 flag 可能只是:

Workaround

而不是:

Root Fix

所以我現在會問:

這個修改是在修原因?

還是在壓症狀?

Workaround 不是永遠不能用。

有時候因為:

時程
風險
外部限制

確實需要先擋。

但至少要知道:

我現在做的是哪一種。


Debug 不只是「找到錯的那行」

以前我會覺得 Debug 是:

找到錯誤 Code
↓
改掉

現在比較像:

建立模型
↓
找到實際行為和模型不一致的地方
↓
縮小範圍
↓
驗證原因
↓
選擇修法

所以 Debug 本身其實是一種:

推理過程。

我現在常用的 Debug Checklist

如果遇到一個陌生 Bug,

我會先跑這份:

1. 怎麼穩定重現?

2. 正常流程應該怎麼走?

3. 誰觸發這件事?

4. 資料從哪裡來?

5. Source of Truth 是誰?

6. 每一站的值和型別是什麼?

7. Network Request 真正送了什麼?

8. Response 真正回了什麼?

9. 第一個錯誤在哪一層出現?

10. 我現在的修改是在修 Root Cause,
    還是只是在擋症狀?

這十題不一定每次全部需要。

但當我完全不知道問題在哪裡時,

它可以幫我重新找到入口。


DevTools 也開始不只是「看 Error」

以前開 DevTools:

Console 有沒有紅字?

現在會分不同需求:

Console
→ 值、執行順序、Error

Network
→ Request / Response

React DevTools
→ Props / State / Component

Breakpoint
→ 程式到底怎麼走

工具沒有變。

改變的是:

我開始知道自己想確認什麼。


AI 也應該放在這套流程裡

現在 Debug 當然可以問 AI。

例如把錯誤貼進去:

幫我修。

AI 很可能很快就給出一段可以試的 Code。

但如果我自己不知道:

這個資料從哪來
這支 API 為什麼打
這個 Component 為什麼 Render

那很容易變成:

AI 改一版
↓
測
↓
不行
↓
再貼回去
↓
再改一版

最後即使功能好了,

我還是不知道:

到底發生什麼事。

所以我現在比較希望 AI 幫我的方式是:

我先畫資料流
↓
提出目前假設
↓
讓 AI 幫我檢查盲點
↓
一起縮小範圍

而不是:

先把整份 Code 丟出去
↓
等答案

從 Day 01 到現在,我最大的變化其實是問題變了

Day 01 的我比較容易問:

這一行是什麼?

現在遇到 Bug,

開始會問:

誰觸發?

資料從哪來?

中間被轉過幾次?

誰是 Source of Truth?

Request 真正送什麼?

Response 真正回什麼?

哪一層第一次出錯?

語法當然還是會有不懂的。

但現在我比較知道:

什麼時候真的需要查語法。

不是看到陌生 Code 就全部停下來。

而是先把流程抓住。


如果把這 29 天濃縮成一張圖

我現在的 Debug 流程大概是:

看到 Bug
↓
先重現

「什麼操作會發生?」
↓
畫正常流程

「資料應該怎麼走?」
↓
找 Source of Truth

「現在每一站實際是什麼?」
↓
檢查 Value + Type

↓
看 Network Request

「真的送出去什麼?」
↓
看 Response

「Server 真正回什麼?」
↓
找到第一個不符合預期的地方

↓
提出一個假設

↓
做最小修改驗證

↓
確認 Root Cause

↓
選擇 Root Fix / 合理的 Workaround

↓
Regression Test

最後還要再測:

原本的 Bug 好了嗎?

其他正常流程有沒有被我修壞?

因為:

修好一個 Case,不代表修改就結束了。


今天真正想記的是

以前遇到 Bug:

看到錯
↓
改 Code

現在我更希望自己做到:

看到錯
↓
重現
↓
畫流程
↓
追資料
↓
找第一個錯誤
↓
驗證假設
↓
再改 Code

這中間看起來多了很多步驟。

但實際上,

它反而可能讓我少走很多冤枉路。

因為真正讓 Debug 變快的,

不一定是:

我打 Code 的速度變快。

而是:

我越來越快知道「不需要看哪裡」。

明天就是最後一天。

Day 30 我想回到這個系列一開始的問題:

30 天前,我以為看懂程式碼就是看懂每一行;30 天後,我開始知道真正要找的是那條把每一行串起來的線。


上一篇
Day 28|只是顯示 HTML,為什麼還要加 iframe sandbox?
下一篇
Day 30|30 天後,我終於不再從第一行開始看程式了
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言